/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
*/

package com.chasekernan.hxnova.server;

import com.chasekernan.hxnova.core.races.Race;
import com.chasekernan.hxnova.core.dataholders.Global;
import com.chasekernan.hxnova.core.dataholders.Players;
import com.chasekernan.hxnova.core.players.Player;
import com.chasekernan.hxnova.core.players.PlayerRelations;
import com.chasekernan.hxnova.core.dataholders.Map;
import com.chasekernan.hxnova.core.dataholders.FleetHolder;
import com.chasekernan.hxnova.core.dataholders.StarsHolder;
import com.chasekernan.hxnova.core.stars.MapCreator;
import com.chasekernan.hxnova.utils.Vector;
import com.chasekernan.hxnova.utils.XmlBuilder;
import com.chasekernan.hxnova.core.races.ResearchCosts;
import com.chasekernan.hxnova.core.dataholders.SalvageHolder;
import haxe.xml.Fast;
import com.chasekernan.hxnova.utils.Timing;

import langhx.Ensure;
import langhx.Require;

/**
    Contains methods for dealing with the creation and management of games.
**/
class Game {
	
	public var name : String;
	public var data : NewGameData;
    
	/**
		Will be null if the default components are used.
	**/
	public var componentLocation : String;
    
	public function new() {}
    
    /**
		Reads a game specification file.
		
		NOTE: Components don't have to specified. If their location isn't 
		specified, then the server uses the default component set.
		
		Example:
		<game name="Test" 
				component_location="custom_components.hxnova-components">
			<map num_stars="500" type="Ring" outer_radius="500" 
					inner_radius="100"/>
			<races>
				<race name="player 1"> 
					...
				</race>
			</races>
		</game>
		
		or
		
		<game name="Test" >
			<map num_stars="500" type="Rectangle" width="500" 
					height="500"/>
			<races>
				<race name="player 1"> 
					...
				</race>
			</races>
		</game>
			
	**/
    public static function readFromXml(xml : Xml) : Game {
        langhx.Require.notNull(xml, "xml");
        try {
            var fast = new Fast(xml);
			
			var name = fast.att.name;
			var components = if (fast.has.component_location) 
								fast.att.component_location 
							else 
								null;
								
			var map = fast.node.map;
			var numStars = Std.parseInt(map.att.num_stars);
			var type = switch(map.att.type) {
				
				case "rectangle":
					Rectangle(Std.parseInt(map.att.width), 
							  Std.parseInt(map.att.height));
							  
				case "ring":
					Ring(Std.parseInt(map.att.outer_radius),
						 Std.parseInt(map.att.inner_radius));
			};
			
			var races = new Array<Race>();
            if (fast.hasNode.races && fast.node.races.hasNode.race) {
                for (raceNode in fast.node.races.nodes.race) {
                    races.push(Race.readFromXml(raceNode.x));
                }
            }
			
			var g = new Game();
            g.name = name;
            g.componentLocation = components;
            g.data = { map : { numStars : numStars, type : type }, 
					   races : races };
                       
            return g;
        } catch (e : Dynamic) {
            throw "Error while reading game xml file: \n" + e;
        }
    }
    
    public static function writeToXml(g : Game, ?forClient = false) : Xml {
        Require.notNull(g, "g");
        Require.notNull(g.name, "g.name");
        Require.notNull(g.data, "g.data");
        
        if (!forClient) Require.notEmpty(g.data.races, "g.data.races");
        
        var xml = new XmlBuilder(XML_TITLE);
		
		xml.addAttribute("name", g.name);
		xml.addAttribute("component_location", g.componentLocation);
		
		var map = new XmlBuilder("map");
		map.addAttribute("num_stars", g.data.map.numStars);
		
		map.addAttribute("type", switch(g.data.map.type) {
			
			case Rectangle(width, height):
				map.addAttribute("width", width);
				map.addAttribute("height", height);
				"rectangle";
				
			case Ring(outer, inner):
				map.addAttribute("outer_radius", outer);
				map.addAttribute("inner_radius", inner);
				"ring";
				
		});
		
		xml.addChild(map);
		
        if (!forClient) {
            var races = new XmlBuilder("races");
            for (race in g.data.races) races.addClassObj(race);
            xml.addChild(races);
        }
		
		return xml.xml;
    }
    
    /**
        Creates a new game from the game object given. Also calls
        [Server.initGame].
		
		If components is specified, then it attempts to load the components 
		file from the specified location.
		
        Will throw an error if the name is null, races array is null, the num
        stars is less than the number of players, or the map type data is 
        wrong (i.e. negative dimensions).
    **/
    public static function createNewGame(g : Game) {
        Require.notNull(g.name, "g.name");
        Require.notNull(g.data, "g.data");
        Require.notEmpty(g.data.races, "g.data.races");
        Require.isTrue(g.data.map.numStars >= g.data.races.length, 
                      "g.data.map.numStars >= g.data.races.length");
        
        switch(g.data.map.type) {
            
            case Rectangle(width, height):
                Require.isTrue(width > 0, "width");
                Require.isTrue(height > 0, "height");
                
            case Ring(outerRadius, innerRadius):
                Require.isTrue(innerRadius > 0, "innerRadius");
                Require.isTrue(outerRadius > innerRadius,
                              "outerRadius > innerRadius");
        }
		
        Global.players = new Players();
        for(r in g.data.races) {
            var p = new Player();
            p.race = r;
            p.name = r.name;
            p.techLevel = r.startingTech.clone();
            p.designs = new Array();
            p.researchField = {field : ResearchCosts.TECH_TYPES[0], 
                               percent : 0.15};
            p.updateScannerAndDefenseType();
            p.setupBattlePlans();
            Global.players.addPlayer(p);
        }
        
        //player relations needs the Global.players structure
        for (player in Global.players) {
            player.relations = new PlayerRelations(player);
        }
        
        //TODO: Load names from external file.
        var names = new Array<String>();
        for(i in 0...g.data.map.numStars) {
            var name = "";
            for (i in 0...5) name += String.fromCharCode(65 + Std.random(48));
            
            names.push(name);
        }
        
        var stars = switch(g.data.map.type) {
            case Rectangle(width, height):
                //copy used here so that the global players array order isn't 
                //randomized by the map creator.
                MapCreator.generateRectMap(new Vector(width, height), 
                        g.data.map.numStars, Global.players.players.copy(), 
                        names);
            case Ring(outerRadius, innerRadius):
                MapCreator.generateRingMap(outerRadius, innerRadius, 
                        g.data.map.numStars, Global.players.players.copy(),
                        names);
        };
        
        Global.stars = new StarsHolder(stars);
        Global.map = new Map();
        Global.map.addStars(stars);
        Global.salvage = new SalvageHolder();
        Global.fleets = new FleetHolder();
        Global.game = g;
		
        Timing.record("game creation");
        
		Server.initGame(g);
    }
    
    /**
        Creates a new game from the xml file given. Also calls
        [Server.initGame].
		
		If components is specified, then it attempts to load the components 
		file from the specified location.
		
        Will throw an error if the name is null, races array is null, the num
        stars is less than the number of players, or the map type data is 
        wrong (i.e. negative dimensions).
    **/
    public static function createNewGameFromXml(xml : Xml) {
        Require.notNull(xml, "xml");
        
        createNewGame(readFromXml(xml));
	}
	
	public static var XML_TITLE = "game";
    
    public function cloneForPlayer() : Game {
        var g = new Game();
        g.name = name;
        
        g.data = { races : new Array(), map : data.map };
        
        return g;
    }
}

enum MapType {
    Rectangle(width : Int, height : Int);
    Ring(outerRadius : Int, innerRadius : Int);
} 

typedef MapSettings = {
    var numStars : Int;
    var type : MapType;
}

typedef NewGameData = {
    var races : Array<Race>;
    var map : MapSettings;
}
